#!/usr/bin/env node

import {Command} from 'commander';
import inquirer from 'inquirer';
import process from 'process';
import {createLogger} from '../utils/Logger.js';
import {OPSConstant} from '../constant/OPSConstant.js';
import {buildEasySearchQuery} from '../utils/LuceneParser.js';
import {logMixin} from './nb/modules/log.js';
import {EasySearchClient} from '../utils/EasySearchClient.js';
import {ElasticsearchClient} from '../utils/ElasticsearchClient.js';
import {DateUtil} from '../utils/DateUtil.js';
import {getGlobalOpsFile} from '../utils/OpsFile.js';

class AppLogCommand {
    constructor() {
        this.program = new Command();
        this.logger = createLogger('APP-LOG');
        this.globalOpsFile = getGlobalOpsFile();
        // 复用 nb log 的工具方法（解析 lookback、输出、follow 等），排除内部 handleLogCommand 与 createEasySearchClient
        for (const [key, value] of Object.entries(logMixin)) {
            if (['handleLogCommand', 'createEasySearchClient'].includes(key)) {
                continue;
            }
            if (!this[key]) {
                this[key] = value;
            }
        }
    }

    setupProgram() {
        this.program
            .name('app-log')
            .description('在终端查询应用日志，支持 feature/stable/uat/prod 环境，输出风格与 nb log 对齐')
            .argument('[query]', 'Kibana/Lucene 查询表达式，例如 reqId:"xxx" AND logLevel:ERROR，默认不过滤')
            .argument('[lookback]', '回溯时长，默认 2h，格式 30m/2h/1d')
            .option('-f, --feature', 'feature 环境', false)
            .option('-t, --stable', 'stable 环境', false)
            .option('-u, --uat', 'uat 环境', false)
            .option('-p, --prod', 'prod 环境（复用 nb log 的 EasySearch 实现）', false)
            .option('-s, --size <size>', '返回日志条数，默认 50，最大 500')
            .option('--raw', '输出原始 JSON', false)
            .option('--follow', '持续监听匹配日志，类似 tail -f', false)
            .option('--verbose', '打印调试信息', false)
            .action((query, lookback, options) => {
                this.handleAppLog(query, lookback, options).catch(error => {
                    this.logger.error(`日志查询失败: ${error.message}`);
                    if (error.stack) {
                        this.logger.debug(error.stack);
                    }
                    process.exitCode = 1;
                });
            });
    }

    resolveEnv(options) {
        const chosen = [];
        if (options?.feature) chosen.push('feature');
        if (options?.stable) chosen.push('stable');
        if (options?.uat) chosen.push('uat');
        if (options?.prod) chosen.push('prod');
        if (chosen.length > 1) {
            throw new Error('环境选项冲突，请仅选择一个 -f/-t/-u/-p');
        }
        if (chosen.length === 0) {
            return 'feature';
        }
        return chosen[0];
    }

    resolveAppLogIndex(indexBase) {
        let base = typeof indexBase === 'string' ? indexBase.trim() : '';
        if (base) {
            const matched = base.match(/^(.*?)-\d{4}\.\d{2}\.\d{2}$/);
            base = matched ? matched[1] : base;
        }
        if (!base) {
            return `${OPSConstant.APP_LOG_DEFAULT_INDEX}-*`;
        }
        if (base.includes('*')) {
            return base;
        }
        const today = DateUtil.format(new Date(), 'yyyy.MM.dd');
        return `${base}-${today}`;
    }

    parseLogSize(sizeOption) {
        if (sizeOption === undefined || sizeOption === null) {
            return OPSConstant.APP_LOG_DEFAULT_PAGE_SIZE;
        }
        const size = Number.parseInt(sizeOption, 10);
        if (!Number.isFinite(size) || size <= 0) {
            throw new Error('日志条数必须为正整数');
        }
        if (size > OPSConstant.APP_LOG_MAX_PAGE_SIZE) {
            this.logger.warn(`日志条数 ${size} 超出最大限制 ${OPSConstant.APP_LOG_MAX_PAGE_SIZE}，已自动截断`);
            return OPSConstant.APP_LOG_MAX_PAGE_SIZE;
        }
        return size;
    }

    createEasySearchClient(verbose) {
        const username = this.getFirstConfigValue([
            'easysearch_username',
            'easysearch_userName',
            'easysearch_user',
            'ops_user'
        ]);
        const password = this.getFirstConfigValue([
            'easysearch_password',
            'easysearch_pass',
            'ops_password'
        ]);
        if (!username || !password) {
            throw new Error('未配置 EasySearch 登录信息，请在 ops_global.properties 中补充 easysearch_username 与 easysearch_password');
        }
        return new EasySearchClient(username, password, verbose);
    }

    createElasticsearchClient(verbose) {
        return new ElasticsearchClient(OPSConstant.APP_LOG_ES_BASE_URL, verbose);
    }

    renderEnvLabel(env) {
        const mapping = {
            feature: 'Feature',
            stable: 'Stable',
            uat: 'UAT',
            prod: 'PROD (EasySearch)'
        };
        return mapping[env] || env;
    }

    async handleAppLog(rawQuery, rawLookback, options) {
        const luceneQuery = (rawQuery ?? '').trim();
        const hasQuery = Boolean(luceneQuery);
        const env = this.resolveEnv(options);
        const lookback = this.parseLookback(rawLookback);
        const size = this.parseLogSize(options.size);
        const follow = Boolean(options.follow);
        if (follow && options.raw) {
            throw new Error('follow 模式暂不支持 --raw 输出，请移除 --raw 后重试');
        }
        const {indexBase, query} = hasQuery
            ? buildEasySearchQuery(luceneQuery)
            : {indexBase: null, query: {match_all: {}}};
        const {start, end} = this.buildTimeRange(lookback.durationMs);
        const timedQuery = this.appendTimeRangeFilter(query, start, end);
        const appIndexBase = indexBase || OPSConstant.APP_LOG_DEFAULT_INDEX;
        const indexName = env === 'prod'
            ? this.resolveEasySearchIndex(indexBase || null)
            : this.resolveAppLogIndex(appIndexBase);
        const client = env === 'prod'
            ? this.createEasySearchClient(Boolean(options.verbose))
            : this.createElasticsearchClient(Boolean(options.verbose));
        const searchParams = {
            index: indexName,
            body: this.buildSearchBody(timedQuery, size, 'asc')
        };

        this.logger.step(1, `查询 ${this.renderEnvLabel(env)} 索引: ${indexName}`);
        this.logger.info(
            `时间范围 ${DateUtil.format(start, 'yyyy-MM-dd HH:mm:ss')} ~ ${DateUtil.format(end, 'yyyy-MM-dd HH:mm:ss')}，回溯 ${lookback.label}，单页返回 ${size} 条日志（按时间正序，最新在最下方）`
        );
        if (options.verbose) {
            this.logger.info(`请求参数: ${JSON.stringify(searchParams)}`);
        }

        const response = await client.search(searchParams);
        const totalInfo = this.extractTotalInfo(response);
        this.printSearchSummary(response, size);
        this.warnIfLargeTotal(totalInfo);
        if (follow) {
            const initialHits = this.extractHits(response);
            await this.streamLog({
                client,
                indexName,
                baseQuery: query,
                size,
                pollInterval: OPSConstant.EASY_SEARCH_FOLLOW_INTERVAL_MS,
                initialStart: start,
                initialHits
            });
            return;
        }
        if (options.raw) {
            console.log(JSON.stringify(response, null, 2));
            return;
        }
        await this.paginateLogs({
            client,
            indexName,
            baseQuery: timedQuery,
            pageSize: size,
            initialResponse: response,
            totalInfo
        });
    }

    extractTotalInfo(response) {
        const totalValue = response?.hits?.total?.value;
        const relation = response?.hits?.total?.relation;
        const total = Number.isFinite(totalValue) ? totalValue : 0;
        return {
            total,
            relation: relation || 'eq'
        };
    }

    warnIfLargeTotal(totalInfo) {
        const total = totalInfo?.total ?? 0;
        const relation = totalInfo?.relation ?? 'eq';
        if (relation === 'gte' || total >= OPSConstant.APP_LOG_LARGE_TOTAL_WARN) {
            const label = this.renderTotalLabel(total, relation);
            this.logger.warn(`匹配结果 ${label} 条，数据量较大，分页加载可能耗时较久，建议追加过滤条件`);
        }
    }

    renderTotalLabel(total, relation) {
        const safeTotal = Number.isFinite(total) ? total : 0;
        return relation === 'gte' ? `${safeTotal}+` : `${safeTotal}`;
    }

    async paginateLogs({client, indexName, baseQuery, pageSize, initialResponse, totalInfo}) {
        const total = totalInfo?.total ?? 0;
        const relation = totalInfo?.relation ?? 'eq';
        const totalLabel = this.renderTotalLabel(total, relation);
        let loaded = 0;
        let currentResponse = initialResponse;

        while (true) {
            const hits = this.extractHits(currentResponse);
            if (hits.length === 0) {
                if (loaded === 0) {
                    this.logger.warn('未查询到符合条件的日志');
                } else {
                    this.logger.info('没有更多日志可加载');
                }
                break;
            }
            if (loaded === 0) {
                this.logger.info('按时间正序展示，最新日志在最下方');
            }
            this.printHits(hits, loaded + 1);
            loaded += hits.length;

            if ((relation === 'eq' && loaded >= total) || hits.length < pageSize) {
                const suffix = relation === 'eq' ? `/${total}` : '';
                this.logger.success(`已加载 ${loaded}${suffix} 条日志`);
                break;
            }

            const shouldContinue = await this.promptLoadMore(loaded, totalLabel, pageSize);
            if (!shouldContinue) {
                this.logger.info(`已累计加载 ${loaded} 条日志，用户选择停止`);
                break;
            }
            try {
                currentResponse = await client.search({
                    index: indexName,
                    body: this.buildSearchBody(baseQuery, pageSize, 'asc', loaded)
                });
            } catch (error) {
                this.logger.error(`加载更多日志失败：${error.message}`);
                break;
            }
        }
    }

    async promptLoadMore(loaded, totalLabel, pageSize) {
        const {shouldContinue} = await inquirer.prompt([
            {
                type: 'confirm',
                name: 'shouldContinue',
                message: `已加载 ${loaded}/${totalLabel} 条日志，是否继续加载下一批 ${pageSize} 条更晚的日志（时间更新）？`,
                default: false
            }
        ]);
        return Boolean(shouldContinue);
    }

    async run(argv) {
        this.setupProgram();
        try {
            await this.program.parseAsync(argv, {from: 'user'});
        } catch (error) {
            this.logger.error(`执行失败: ${error.message}`);
            if (error.stack) {
                this.logger.debug(error.stack);
            }
            process.exitCode = 1;
        }
    }
}

export async function execute(args) {
    const handler = new AppLogCommand();
    await handler.run(args);
}
